package com.tyron.builder.gradle.internal.tasks

import com.android.SdkConstants
import com.android.ide.common.resources.FileStatus
import com.android.ide.common.symbols.generateKeepRulesFromLayoutXmlFile
import com.android.ide.common.symbols.generateMinifyKeepRules
import com.android.ide.common.symbols.parseManifest
import com.android.ide.common.symbols.parseMinifiedKeepRules
import com.android.resources.ResourceFolderType
import com.tyron.builder.api.variant.impl.BuiltArtifactsLoaderImpl
import com.tyron.builder.files.SerializableChange
import com.tyron.builder.gradle.internal.component.ComponentCreationConfig
import com.tyron.builder.gradle.internal.scope.InternalArtifactType
import com.tyron.builder.gradle.internal.tasks.factory.VariantTaskCreationAction
import com.tyron.builder.tasks.IncrementalTask
import com.tyron.builder.tasks.getChangesInSerializableForm
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.Logging
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.work.Incremental
import org.gradle.work.InputChanges
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import java.io.File
import java.nio.file.Files
import javax.xml.parsers.DocumentBuilderFactory

/**
 * Generates the Proguard keep rules file `aapt_rules.txt`. In the past this was generated by AAPT,
 * but since we don't call AAPT(2) for packaging library resources we are generating the file
 * ourselves.
 *
 * This task takes the merged manifest and the merged resources directory (local only, small merge),
 * collects information from them and generates the keep rules file.
 */
@CacheableTask
@BuildAnalyzer(primaryTaskCategory = TaskCategory.OPTIMIZATION)
abstract class GenerateLibraryProguardRulesTask : IncrementalTask() {

    @get:OutputFile
    abstract val proguardOutputFile: RegularFileProperty

    @get:InputFiles
    @get:PathSensitive(PathSensitivity.RELATIVE)
    abstract val manifestFiles: DirectoryProperty

    @get:Incremental
    @get:InputFiles
    @get:PathSensitive(PathSensitivity.RELATIVE)
    abstract val inputResourcesDir: DirectoryProperty

    override fun doTaskAction(inputChanges: InputChanges) {
        val isIncremental = inputChanges.isIncremental
        val manifest =
            BuiltArtifactsLoaderImpl().load(manifestFiles)?.elements?.first()?.outputFile
                ?: throw RuntimeException("Cannot find manifest file")
        val changedResources = if (isIncremental) {
            inputChanges.getChangesInSerializableForm(inputResourcesDir).changes
        } else {
            emptyList()
        }
        workerExecutor.noIsolation().submit(
            GenerateProguardRulesWorkAction::class.java
        ) {
//            it.initializeFromAndroidVariantTask(this)
            it.manifestFile.set(File(manifest))
            it.proguardOutputFile.set(proguardOutputFile)
            it.inputResourcesDir.set(inputResourcesDir)
            it.changedResources.set(changedResources)
            it.incremental.set(inputChanges.isIncremental)
        }
    }

    abstract class GenerateProguardRulesWorkAction
        : WorkAction<GenerateProguardRulesWorkAction.Params>
    {
        abstract class Params : WorkParameters {
            abstract val manifestFile: RegularFileProperty
            abstract val proguardOutputFile: RegularFileProperty
            abstract val inputResourcesDir: DirectoryProperty
            abstract val changedResources: ListProperty<SerializableChange>
            abstract val incremental: Property<Boolean>
        }

        override fun execute() {
            if (canBeProcessedIncrementally(parameters)) {
                runIncrementalTask(parameters)
                return
            }
            runFullTask(parameters)
        }

        private fun canBeProcessedIncrementally(params: Params): Boolean =
            params.changedResources.get().all { canResourcesBeProcessedIncrementally(it) }
                    && params.incremental.get()
    }

    class CreationAction(
        creationConfig: ComponentCreationConfig
    ): VariantTaskCreationAction<GenerateLibraryProguardRulesTask, ComponentCreationConfig>(
        creationConfig
    ) {

        override val name: String
            get() = computeTaskName("generate", "LibraryProguardRules")
        override val type: Class<GenerateLibraryProguardRulesTask>
            get() = GenerateLibraryProguardRulesTask::class.java

        override fun handleProvider(
            taskProvider: TaskProvider<GenerateLibraryProguardRulesTask>
        ) {
            super.handleProvider(taskProvider)
            creationConfig.artifacts.setInitialProvider(
                taskProvider,
                GenerateLibraryProguardRulesTask::proguardOutputFile
            ).withName(SdkConstants.FN_AAPT_RULES).on(InternalArtifactType.AAPT_PROGUARD_FILE)
        }

        override fun configure(
            task: GenerateLibraryProguardRulesTask
        ) {
            super.configure(task)

            creationConfig.artifacts.setTaskInputToFinalProduct(
                InternalArtifactType.PACKAGED_RES,
                task.inputResourcesDir
            )

             creationConfig.artifacts.setTaskInputToFinalProduct(
                 InternalArtifactType.PACKAGED_MANIFESTS, task.manifestFiles)
        }
    }
}

internal fun canResourcesBeProcessedIncrementally(resourceChanges: SerializableChange): Boolean =
  when (resourceChanges.fileStatus) {
      FileStatus.NEW -> true
      FileStatus.CHANGED -> !isLayoutFile(resourceChanges.file)
      FileStatus.REMOVED -> !isLayoutFile(resourceChanges.file)
      else -> false
  }

internal fun runFullTask(
    params: GenerateLibraryProguardRulesTask.GenerateProguardRulesWorkAction.Params) {
    // Generate `aapt_rules.txt` containing keep rules for Proguard.
    Files.write(
      params.proguardOutputFile.get().asFile.toPath(),
      generateMinifyKeepRules(
        parseManifest(params.manifestFile.get().asFile), params.inputResourcesDir.get().asFile)
    )
}

internal fun runIncrementalTask(
  params: GenerateLibraryProguardRulesTask.GenerateProguardRulesWorkAction.Params) {
    if (!params.proguardOutputFile.get().asFile.exists()) {
        Logging.getLogger(GenerateLibraryProguardRulesTask::class.java)
          .warn("Cannot find file: ${params.proguardOutputFile.get().asFile.path}")
        runFullTask(params)
        return
    }
    val addedLayoutFiles = params.changedResources.get()
      .filter {
          isLayoutFile(it.file) && it.fileStatus == FileStatus.NEW
      }
      .map {
          it.file
      }
    if (addedLayoutFiles.none()) {
        return
    }
    val currentKeepRules = parseMinifiedKeepRules(params.proguardOutputFile.get().asFile)
    val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
    addedLayoutFiles.forEach { addedLayoutFile ->
        generateKeepRulesFromLayoutXmlFile(
          addedLayoutFile, documentBuilder, currentKeepRules)
    }
    val contentsToWrite =
      "# Generated by the gradle plugin\n${currentKeepRules.joinToString("\n")}"
        .toByteArray()
    Files.write(params.proguardOutputFile.get().asFile.toPath(), contentsToWrite)
}

private fun isLayoutFile(file: File): Boolean =
  ResourceFolderType.getFolderType(file.parentFile.name) == ResourceFolderType.LAYOUT
